package com.gzx.plugin.tjzy.core.utils.crypter;


import com.google.common.hash.Hashing;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;

public class AesUtil {

    /**
     * 算法名
     */
    public static final String ALGORITHM = "AES";
    /**
     * 转换算法，格式为：算法/回馈模式/填充模式
     */
    public static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    /**
     * 计算IV的算法
     * (明文) -> IV
     * 应保证:
     * 1. 结果的长度为固定16字节(128bit)
     * 2. 参数不同，结果尽量不同，以保证加密安全性
     * 3. 参数相同，结果也相同，以使得相同的(明文, 密钥)有相同的密文
     */
    public static final Function <byte[], byte[]> IV_FUNCTION = raw -> Hashing.md5().hashBytes(raw).asBytes();

    /**
     * IV长度
     */
    public static final int IV_LEN = 16;



    /**
     * 加密
     *
     * @param raw 明文
     * @param key 密钥，长度应为16
     * @param iv  IV，长度应为16
     * @return 密文
     */
    public static byte[] encrypt(byte[] raw, byte[] key, byte[] iv) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
            IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParamSpec);
            return cipher.doFinal(raw);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }


    /**
     * 加密，IV 为 md5(raw)，结果为 [iv, 密文]
     */
    public static byte[] encrypt(byte[] raw, byte[] key) {
        byte[] iv = IV_FUNCTION.apply(raw);
        byte[] encrypt = encrypt(raw, key, iv);
        byte[] result = new byte[IV_LEN + encrypt.length];
        System.arraycopy(iv, 0, result, 0, IV_LEN);
        System.arraycopy(encrypt, 0, result, IV_LEN, encrypt.length);
        return result;
    }

    /**
     * 对文本进行加密，IV 为 md5(raw)，结果为 base64ForUrl([iv, 密文])
     */
    public static String encryptText(String raw, byte[] key) {
        if (raw.isBlank()) {
            return raw;
        }
        byte[] rawBytes = raw.getBytes(StandardCharsets.UTF_8);
        byte[] encBytes = encrypt(rawBytes, key);
        return EncodeUtil.encodeToBase64ForUrl(encBytes);
    }

    /**
     * 解密
     * @param enc 密文
     * @param key 密钥，长度应为16
     * @param iv IV，长度应为16
     * @return 明文
     */
    public static byte[] decrypt(byte[] enc, byte[] key, byte[] iv) {
        try {
            SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
            IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParamSpec);
            return cipher.doFinal(enc);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    /**
     * 解密文本，
     * 密文应由文本加密而来
     *
     * @param enc 密文(使用Base64ForUrl编码)
     * @param key 密钥，长度应为16
     * @param iv IV，长度应为16
     * @return 明文
     */
    public static String decryptText(String enc, byte[] key, byte[] iv) {
        if (enc.isBlank()) {
            return enc;
        }
        byte[] encBytes = EncodeUtil.decodeBase64ForUrl(enc);
        byte[] rawBytes = decrypt(encBytes, key, iv);
        return new String(rawBytes, StandardCharsets.UTF_8);
    }

    /**
     * 解密，
     * @param enc [iv, 密文]
     * @param key 密钥
     * @return 明文
     */
    public static byte[] decrypt(byte[] enc, byte[] key) {
        byte[] iv = new byte[IV_LEN];
        byte[] encBytes = new byte[enc.length - IV_LEN];
        System.arraycopy(enc, 0, iv, 0, IV_LEN);
        System.arraycopy(enc, IV_LEN, encBytes, 0, encBytes.length);
        return decrypt(encBytes, key, iv);
    }

    /**
     * 解密文本，
     * 密文应由文本加密而来
     *
     * @param enc 密文(base64ForUrl([iv,密文]))
     * @param key 密钥
     * @return 明文
     */
    public static String decryptText(String enc, byte[] key) {
        if (enc.isBlank()) {
            return enc;
        }
        byte[] encBytes = EncodeUtil.decodeBase64ForUrl(enc);
        byte[] rawBytes = decrypt(encBytes, key);
        return new String(rawBytes, StandardCharsets.UTF_8);
    }




}
